package bid

import (
	"errors"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"strconv"
	"strings"
	"sync"
	"time"
	"wx-api/adx-bid/cache"
	"wx-api/adx-bid/config"
	"wx-api/adx-bid/model/m_channelslot"
)

type BudgetSlotLimit struct{}

func (z *BudgetSlotLimit) CkBudgetSlotLimit(s_info *m_channelslot.ChannelSlot) (bool, error, string) {
	req_t, err_req := z.LRate(s_info.Uqid, &s_info.LimitReqRate, "req")
	pv_t, err_pv := z.LRate(s_info.Uqid, &s_info.LimitPvRate, "pv")
	ck_t, err_ck := z.LRate(s_info.Uqid, &s_info.LimitCkRate, "ck")
	rr := []string{}
	if s_info.LimitPvRate.IsSet {
		rr = append(rr, "pv")
	}
	if s_info.LimitCkRate.IsSet {
		rr = append(rr, "ck")
	}
	if !ck_t {
		return false, err_ck, strings.Join(rr, ",")
	}
	if !pv_t {
		return false, err_pv, strings.Join(rr, ",")
	}
	if !req_t {
		return false, err_req, strings.Join(rr, ",")
	}
	//记录总数
	go z.ReqSlotLimit(s_info.Uqid, "req")
	return true, nil, strings.Join(rr, ",")
}

func (z *BudgetSlotLimit) LRate(sid string, s *m_channelslot.LRate, ty string) (bool, error) {
	if !s.IsSet {
		return true, nil
	}
	//匀速投放
	if s.IsYunSu {
		return z.DLimitYunSu(sid, s, ty)
	}
	//天控制
	t_d, err := z.Date(sid, s, ty)
	if !t_d {
		return false, err
	}
	//小时控制
	t_h, errh := z.DateHour(sid, s, ty)
	if !t_h {
		return false, errh
	}
	return true, nil
}

var LimitDLimitDate = new(sync.Map)

func (z *BudgetSlotLimit) Date(sid string, s *m_channelslot.LRate, ty string) (bool, error) {
	if s.DNum == 0 {
		return true, nil
	}
	key := z.VatSlotLimitDateKey(sid, ty)
	tmp_rnum, ok := LimitDLimitDate.Load(key)
	if ok && tmp_rnum.(int64) > s.DNum {
		return false, errors.New("Date LimitDLimitDate limit:" + strconv.FormatInt(tmp_rnum.(int64), 10) + "=" + strconv.FormatInt(s.DNum, 10))
	}
	rnum, ok, tmsg := z.DLGetSNum(key) //从redis获取缓存
	if !ok {
		return false, errors.New("Date DLGetSNum err:" + tmsg)
	}
	if rnum > s.DNum {
		LimitDLimitDate.Store(key, rnum)
		return false, errors.New("Date LimitDLimitDate limit:" + strconv.FormatInt(rnum, 10) + "=" + strconv.FormatInt(s.DNum, 10))
	}
	return true, nil
}

var LimitDLimitHour = new(sync.Map)

func (z *BudgetSlotLimit) DateHour(sid string, s *m_channelslot.LRate, ty string) (bool, error) {
	if s.HNum == 0 && len(s.HItems) == 0 {
		return true, nil
	}
	key := z.VatSlotLimitHourKey(sid, ty)
	tmp_rnum, ok := LimitDLimitHour.Load(key)
	if ok && tmp_rnum.(int64) > s.HNum {
		return false, errors.New("DateHour LimitDLimitHour limit" + strconv.FormatInt(tmp_rnum.(int64), 10) + "=" + strconv.FormatInt(s.HNum, 10))
	}
	//匹配的小时数
	hhh, _ := cache.CacheChannelSlotLrateMapGet(ty + "-" + sid + "-" + strconv.Itoa(time.Now().Hour()))
	if hhh > 0 && tmp_rnum.(int64) > hhh {
		return false, errors.New("DateHour limit:" + strconv.FormatInt(tmp_rnum.(int64), 10) + "=" + strconv.FormatInt(hhh, 10))
	}
	if s.HNum == 0 && hhh == 0 { //未设置，放行
		return true, nil
	}
	rnum, ok, tmsg := z.DLGetSNum(key) //从redis获取缓存
	if !ok {
		fmt.Println("DLGetSNum DateHour error:" + tmsg)
		return false, errors.New("DateHour DLGetSNum err:" + tmsg)
	}
	//小时优先匹配
	if (hhh > 0 && rnum > hhh) || (s.HNum > 0 && rnum > s.HNum) { //缓存数据大于自定义小时数或每小时总量
		LimitDLimitHour.Load(key)
		return false, errors.New("DateHour DLGetSNum limit:" + strconv.FormatInt(rnum, 10) + "=" + strconv.FormatInt(hhh, 10) + "=" + strconv.FormatInt(s.HNum, 10))
	}
	return true, nil
}

var LimitDLimitYunSu = new(sync.Map)

// 匀速放量
func (z *BudgetSlotLimit) DLimitYunSu(sid string, s *m_channelslot.LRate, ty string) (bool, error) {
	num := int64(0)
	isd := true
	if s.DNum > 0 {
		isd = true
		num = s.DNum
	}
	if s.HNum > 0 {
		isd = false
		num = s.HNum
	}
	//匹配的小时数
	hhh, ok := cache.CacheChannelSlotLrateMapGet(ty + "-" + sid + "-" + strconv.Itoa(time.Now().Hour()))
	if hhh > 0 && ok {
		isd = false
		num = hhh
	}
	if num == 0 { //全为0，放行
		return true, nil
	}
	key := z.VatSlotLimitHourMKey(sid, ty)
	tttt, ok := LimitDLimitYunSu.Load(key)
	if ok {
		return false, errors.New("DLimitYunSu LimitDLimitYunSu is limit:" + strconv.FormatInt(tttt.(int64), 10))
	}
	rnum, ok, tmsg := z.DLGetSNum(key) //从redis获取缓存
	if !ok {
		return false, errors.New("DLimitYunSu DLGetSNum err:" + tmsg)
	}
	if isd {
		//按天计算
		tdlr := GetDateMinuRateLn(num)
		if rnum > tdlr {
			LimitDLimitYunSu.Store(key, rnum)
			return false, errors.New("DLimitYunSu GetDateMinuRateLn limit:" + strconv.FormatInt(rnum, 10) + "=" + strconv.FormatInt(tdlr, 10))
		}
	} else {
		//按小时计算
		tdlr := GetHourMinuRateLn(num)
		if rnum > tdlr {
			LimitDLimitYunSu.Store(key, rnum)
			return false, errors.New("DLimitYunSu GetHourMinuRateLn limit:" + strconv.FormatInt(rnum, 10) + "=" + strconv.FormatInt(tdlr, 10))
		}
	}
	return true, nil
}
func (z *BudgetSlotLimit) DLGetSNum(key string) (int64, bool, string) {
	conn := config.AdxRedisSlotLimit.Get()
	if conn == nil {
		return 0, false, "redis init error"
	}
	defer conn.Close()
	vale, err := redis.Int64(conn.Do("GET", key))
	if err != nil {
		if errors.Is(err, redis.ErrNil) {
			return 0, true, ""
		}
		return 0, false, "redis error2:" + err.Error()
	}
	return vale, true, ""
}

// 记录数据
func (z *BudgetSlotLimit) ReqSlotLimit(sid string, ty string) {
	z.DLSetSNum(z.VatSlotLimitDateKey(sid, ty))  //天计数
	z.DLSetSNum(z.VatSlotLimitHourKey(sid, ty))  //小时计数
	z.DLSetSNum(z.VatSlotLimitHourMKey(sid, ty)) //匀速计数
}
func (z *BudgetSlotLimit) DLSetSNum(key string) {
	conn := config.AdxRedisSlotLimit.Get()
	if conn == nil {
		fmt.Println("DLSetSNum redis init error")
		return
	}
	defer conn.Close()
	_, err := conn.Do("INCR", key)
	if err != nil {
		fmt.Println("BudgetSlotLimit DLSetSNum:" + err.Error())
	}
	return
}
func (z *BudgetSlotLimit) VatSlotLimitDateKey(code, vat string) string {
	return fmt.Sprintf("%s-dl-d-%s=%s", vat, time.Now().Format("20060102"), code)
}
func (z *BudgetSlotLimit) VatSlotLimitHourKey(code, vat string) string {
	return fmt.Sprintf("%s-dl-h-%s=%s", vat, time.Now().Format("2006010215"), code)
}
func (z *BudgetSlotLimit) VatSlotLimitHourMKey(code, vat string) string {
	return fmt.Sprintf("%s-dl-m-%s=m%s-%s", vat, time.Now().Format("2006010215"), GetHourMinuRateLv(), code)
}

// 计算粒度:每3分钟记录一次
var HourMinuRateLvs = 3

// 计算粒度key
func GetHourMinuRateLv() string {
	return strconv.Itoa(int(time.Now().Minute() / HourMinuRateLvs))
}

// 计算粒度内限制个数
func GetHourMinuRateLn(s int64) int64 {
	return int64(s) / int64(60/HourMinuRateLvs)
}
func GetDateMinuRateLv() string {
	return strconv.Itoa(time.Now().Hour()) + strconv.Itoa(int(time.Now().Minute()/HourMinuRateLvs))
}
func GetDateMinuRateLn(s int64) int64 {
	return int64(s) / int64((24*60)/HourMinuRateLvs)
}
